#!/usr/bin/perl

$TL_VERSION="0.1e";
$debug=0;

use LC::log; 
use LC::config;
use LC::UI;
use LC::parse;
use LC::Expand;
use LC::gag;
use Socket;
use Fcntl;
use IO::Socket;
use IO::Select;
use Curses;
use POSIX;
$|=1;

# file globals
my $sock;

# globals
$have_pseudo=0;
$last_alarm=0;
$next_alarm=0;
$wakeup=10;     # how often to exit the select loop to do status processing,
                # etc.
$password_mode=0;

## main ##
LC::config::init();
exp_init();


ui_start();
ui_attr('sender','bold');
ui_attr('dest','bold');
ui_attr('b','bold');
if (! $config{mono}) {
    ui_attr('status_line',  'fg:yellow',  'bg:blue',  'bold');
    ui_attr('input_line',   'fg:white',   'bg:black', 'bold');
    ui_attr('text_window',  'fg:white',   'bg:black', 'normal');
    ui_attr('privhdr',      'fg:green',   'bg:black');
    ui_attr('privmsg',      'fg:green',   'bg:black');
    ui_attr('pubhdr',       'fg:cyan',    'bg:black');
    ui_attr('pubmsg',       'fg:white',   'bg:black');
    ui_attr('emote',        'fg:cyan',    'bg:black');
    ui_attr('review',       'fg:magenta', 'bg:black');
    ui_attr('usersend',     'fg:red',     'bg:black');
    #ui_attr('time',         'fg:magenta', 'bg:black',  'bold');
    ui_attr('whiteblue',    'fg:white',   'bg:blue');
    ui_attr('greenblue',    'fg:green',   'bg:blue',  'bold');
    ui_attr('yellow',       'fg:yellow',  'bg:black',  'bold');
    ui_attr('green',        'fg:green',   'bg:black',  'bold');
} else {
    ui_attr('status_line', 'reverse');
}

tie $page_status, 'LC::status_update', 'page_status';

show_banner();
login();
$SIG{ALRM}=\&foo;
connect_until("*** Disconnected ***");    
log_notice("Exiting.");

END {
    ui_end;
}

##############################################################################
sub login {   
    $parse_state="login";
    log_notice("Connecting to $config{server} $config{port}.");
    set_status(server => $config{server},
	       status => "INIT");

    $sock = IO::Socket::INET->new(PeerAddr => $config{server},
				  PeerPort => $config{port},
				  Proto    => 'tcp');                         
    if (! defined($sock)) { log_err("IO::Socket: $!\n"); }
    
    fcntl($sock,F_SETFL,O_NONBLOCK) || log_err("fcntl: $!\n");
    
    set_status(status => "CONN");
    
    log_notice("Connected.");
    
    pass_until("login:",10);
    set_client_options(); # if (!$config{options_after_connect});

    if ($config{login} && $config{pass}) {
	log_info("Sending autologin ($config{login})\n");
	send_to_server("$config{login} $config{pass}\n");
    }

    connect_until("***");
    set_client_options() if ($config{options_after_connect});
    set_status(status => "ONLINE");
    log_info("Connected");
}


# allow the user to type, a nice normal bidirectional connection until we
# see the desired output.
sub connect_until {
    my ($waitfor)=@_;
    my ($str,$t,$u);

    $waitfor=~ s/\*/\\\*/g;
    $waitfor=~ s/\./\\\./g;

    my $s = IO::Select->new();    

    $s->add($sock);
    $s->add(\*STDIN);

    $next_alarm=30; # kicked up by a %connected event or %c command.

    $last_alarm=time();
    while (1) {
	if (time() > ($last_alarm+$next_alarm)) {
	    alarm_handler();
	}

	@ready=$s->can_read($wakeup);	

	$str="";
	foreach $rs (@ready) {      
	    if ($rs == $sock) {
		my @lines = read_from_server($rs);

		my $end = 0;
		foreach $str (@lines) {
		    # workaround for %recip_regexp at blurb prompt
		    if ($str =~ s/^--> (%.*)$/--> /) {
			parse_line('--> ');
			parse_servercmd($1);
		    } elsif ($str =~ /^%g(.*)/) {
			parse_servercmd('%g');
			parse_line($1);
		    } elsif ($str =~ /^%/) {
			parse_servercmd($str);
		    } else {
			parse_line($str);
		    }
		    $end = 1 if ($str =~ /$waitfor/);
		}
		return if ($end);

		next;
	    }
	    
	    if ($rs == \*STDIN) {
		# check for UI events, deal wid' em.
		while (1) {
		    # get a line of input from the user if it's available.
		    my $line=ui_process();		
		    if (defined($line)) {		    
			my $qline = $line; $qline =~ s/[\<\\]/\\$&/g;

			if ($password_mode) {
			    $password_mode = 0;
			    ui_password(0);
			} else {
			    ui_output("<usersend>" . $qline . "</usersend>");
			}

			if ($line =~ /^\!(.*)/) { 
			    # !command handling
			    ui_output(`$1`); 
			} elsif ($line =~ /^%/) { 
			    # handle client commands
			    client_command($line);
			} elsif ($line =~ /^([^:;]*)[;:]/) {
			    # handle recipient caching for ;
			    exp_set('recips', $1);
			    send_to_server($line . "\n");
			} elsif ($line =~ /^\s*\/info set/i) {
			    # handle /info set command.  
			    $line =~ s/\s*\/info set\s*//g;
			    set_info($line);
			} else {
			    # otherwise, send on to the server.
			    send_to_server($line . "\n");
			}
		    } else {
#			log_debug("line undef"); 
			last;
		    }
		}
		next;
	    }       	
	    
	    log_err("SKY IS FALLING!\n");
	}
    }   
}

# pass through all output from the server until a string is matched.
sub pass_until {
    my ($waitfor,$timeout)=@_;
    my ($t,$str,$matched) = ('', '', 0);
    
    my $s = IO::Select->new();

    $s->add($sock);

    my $stime=time();
    my $now=$stime;
    while ((!$matched) && ($now-$stime < $timeout)) {	
	if ($s->can_read($stime+$timeout-$now)) {
	    my(@lines) = read_from_server($sock);
	    foreach (@lines) {
		parse_line($_);
		if (/$waitfor/) {
		    $matched=1;
		}
	    }
	}
	$now=time();
    }

    if (! $matched) {
	log_err("Error waiting for $waitfor, $timeout sec timeout expired.\n");
    }
}


my $crumb = '';
sub read_from_server {
    my ($sock) = @_;

    my $buf;
    if (sysread($sock,$buf,4096) < 1) {
	if ($errno != EAGAIN) {
	    log_err("sysread: $!"); 
	    next;
	}
    }
    
    # Divide into lines.
    $buf = $crumb . $buf;
    my @lines = split /\r?\n/, $buf, -1;
    $crumb = pop @lines;

    # Durned prompts.
    if ($crumb =~ /-->\s*$/ ||
	$crumb =~ /\(y\/n\)\s*$/i ||
	$crumb =~ /^login:/ ||
	$crumb =~ /^password:/) {
	push @lines, $crumb;
	$crumb = '';
    }

    return @lines;
}


sub send_to_server {
    my ($s)=@_;
    
    my $t=$s;
    $t=~s/\n/\[\\n\]/g;
    log_debug("send_to_server: $t");
    if (syswrite($sock,$s,length($s)) != length($s)) {
	log_err("syswrite: $!"); 
    }

}


sub client_command {
    my($command) = @_;

    return if ($command !~ /^(%\w+)\s*(.*)$/);
    my($cmd, $args) = ($1, $2);

    if ($cmd eq '%eval') {
	my $output=eval($args); 
	ui_output($output);
	ui_output($@) if ($@);
    } elsif ($cmd eq '%gag') {
	foreach (split /[\s,]+/, $args) {
	    if ($gagged{tolower($_)}) {
		undef $gagged{tolower($_)};
		ui_output("$_ is no longer gagged.");
	    } else {
		$gagged{tolower($_)} = 1;
		ui_output("$_ is gagged.");
	    }
	}
    } else {
	ui_output("?");
	ui_output("? %eval <code>       - evals a chunk o' perl");
	ui_output("? %gag <user>        - gags a user");
	ui_output("?");
    }
}


sub do_how {
    $cli_command="how";
    
    send_to_server("/how\n");
}

sub do_who_me {
    $cli_command="who me";
    
    send_to_server("/who me\n");
}

sub alarm_handler {
    log_debug("alarm handler");

    if ($parse_state eq undef) {
	# we're idle, so we can send these commands.

	log_debug("alarm handler: idle state ok");	
	log_debug("alarm handler: unable to proceed- $cli_command pending") 
	    if $cli_command;
	

	if (! $have_pseudo) {
	    do_who_me() unless $cli_command;
	}
	
	do_how() unless $cli_command;
    } else {
	log_debug("alarm handler: not in idle state");
    }

    if ($have_pseudo) {
	log_debug("alarm handler, set for 300");
	$next_alarm=300;		
    } else {
	log_debug("alarm handler, set for 5");
	$next_alarm=5;
    }

    $last_alarm=time()	
}


sub set_client_options {
    if ($config{spoof_lclient}) {
	#log_notice("Setting client options (lclient spoofing)..");
	send_to_server("\#\$\# client_version 0.8b-tiger\n");
	send_to_server("\#\$\# client_name lily\n");
    } else {
	#log_notice("Setting client options..");
	send_to_server("\#\$\# client_version $TL_VERSION\n");
	send_to_server("\#\$\# client_name TigerLily\n");
        #The available options are sender, sendgroup,recip_regexp, connected,
        #info_size, usertype, leaf-cmd, leaf-msg, leaf-notify, and prompt.
	#[+-]leaf-all will turn on or off all of the leafing options.
	send_to_server("\#\$\# options +connected +usertype +leaf-msg +leaf-notify +prompt\n");
    }
}


sub set_status {
    my %s2=@_;

    foreach (keys %s2) {
	if ($s2{$_} eq "incr") {
	    $status{$_}++;
	} elsif ($s2{$_} eq "decr") {
	    $status{$_}--;
	} else {
	    $status{$_}=$s2{$_}; 
	}
    }


    my @left;
    my $name=$status{pseudo};
    $name .= "[$status{blurb}]" if (defined($status{blurb}));
    push @left, $name if length($name);
    push @left,"Parse: $LC::parse::parse_state" if $debug;
    my @right;
    push @right, "$status{here} Here|$status{away} Away|$status{detached} Detach"
                                     if (defined($status{detached}));
    push @right, $status{server}     if (defined($status{server}));
    push @right, $status{status}     if (defined($status{server}));
    
    my $left=join ' | ',@left;
    my $right=join ' | ',@right;
    my $ll=length($left);
    my $lr=80-$ll;    

    # favor things on the left over the right.
    $fmt="%-$ll.$ll" . "s%$lr.$lr" . "s";
    $status_line=sprintf($fmt,$left,$right);

    $status_line =~ s:\|:<whiteblue>\|</whiteblue>:g;
    $status_line =~ s:ONLINE:<greenblue>ONLINE</greenblue>:;

    # -- MORE -- prompt
    if (length($status{page_status})) {
	$status_line="                                 -- $status{page_status} -- ";
    }
    
    ui_status($status_line);

      
}

sub set_info {
    # rather than using the chee-z line editor in emacs, we fire off an editor
    # to let them edit the /info file.
    my($disc)=@_;
    
    my $EDITOR=$ENV{VISUAL} || $ENV{EDITOR} || "vi";
    
    ui_end();
    unlink ("/tmp/tlily.$$");
    if (@info) {
	open(F,">/tmp/tlily.$$");
	foreach (@info) { chomp; print F "$_\n"; }
	close(F);
    }

    system("$EDITOR /tmp/tlily.$$");
    ui_start();

    if (open(F,"</tmp/tlily.$$")) {
	@info=<F>;
	close(F);
	unlink ("/tmp/tlily.$$");
	my $size=@info;

	$cli_command="info set";
	send_to_server("\#\$\# export_file info $size $disc\n");
    } else {
	log_notice("info buffer file not found.");
    }
    
}

sub show_banner {

    ui_output(qq[
<yellow>     ("`-/")_.-'"``-._ </yellow>
<yellow>      . . `; -._    )-;-,_`)</yellow>          <green>TigerLily $TL_VERSION</green>
<yellow>     (v_,)'  _  )`-.\  ``-'</yellow>             <b>"Feel Free"</b>
<yellow>    _.- _..-_/ / ((.'</yellow>
<yellow>  ((,.-'   ((,/ </yellow>
<b>------------------------------------------------------------------------------</b>
]);
}


1;


